import initSqlJs from '@jlongster/sql.js'
import { SQLiteFS } from 'absurd-sql'
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend'

let currentBackendType = 'idb'
let cacheSize = 1024 * 100
let pageSize = 1024 * 10
let dbName = `fts`

let idbBackend = new IndexedDBBackend()
let sqlFS

function memorySizeOf(obj) {
  var bytes = 0

  function sizeOf(obj) {
    if (obj !== null && obj !== undefined) {
      switch (typeof obj) {
        case 'number':
          bytes += 8
          break
        case 'string':
          bytes += obj.length * 2
          break
        case 'boolean':
          bytes += 4
          break
        case 'object':
          bytes += Buffer.byteLength(JSON.stringify(obj))
          break
        default:
          break
      }
    }
    return bytes
  }

  function formatByteSize(bytes) {
    if (bytes < 1024) return bytes + ' bytes'
    else if (bytes < 1048576) return (bytes / 1024).toFixed(3) + ' KiB'
    else if (bytes < 1073741824) return (bytes / 1048576).toFixed(3) + ' MiB'
    else return (bytes / 1073741824).toFixed(3) + ' GiB'
  }

  const num = sizeOf(obj)

  return {
    text: formatByteSize(num),
    num
  }
}

// Helper methods

let SQL = null
let ready = null
async function _init() {
  SQL = await initSqlJs({
    locateFile: file => `/${file}`
  })
  sqlFS = new SQLiteFS(SQL.FS, idbBackend)
  SQL.register_for_idb(sqlFS)

  SQL.FS.mkdir('/blocked')
  SQL.FS.mount(sqlFS, {}, '/blocked')
}

async function init() {
  if (ready) {
    return ready
  }

  ready = await _init()
}

function output(msg) {
  console.log('output>>>', msg)
  postMessage({ type: 'output', msg })
}

function getDBName() {
  return dbName
}

let _db = null
function closeDatabase() {
  if (_db) {
    output(`Closed db`)
    _db.close()
    _db = null
  }
}

async function getDatabase() {
  await init()
  if (_db == null) {
    _db = new SQL.Database(`/blocked/${getDBName()}`, { filename: true })
    // Should ALWAYS use the journal in memory mode. Doesn't make
    // any sense at all to write the journal
    //
    // It's also important to use the same page size that our storage
    // system uses. This will change in the future so that you don't
    // have to worry about sqlite's page size (requires some
    // optimizations)
    _db.exec(`
      PRAGMA cache_size=-${cacheSize};
      PRAGMA page_size=${pageSize};
      PRAGMA journal_mode=MEMORY;
    `)
    _db.exec('VACUUM')

    // create table
    _db.exec(`
      CREATE TABLE IF NOT EXISTS charts (timestamp int, data text);
    `)
    output(
      `Opened ${getDBName()} (${currentBackendType}) cache size: ${cacheSize}`
    )
  }
  return _db
}

getDatabase()

async function insertOne(data) {
  console.log('INSERT_ONE_BEGIN')

  let db = await getDatabase()

  db.exec('BEGIN TRANSACTION')
  let stmt = db.prepare('INSERT INTO charts (timestamp, data) VALUES (?, ?)')

  stmt.run([data.timestamp, JSON.stringify(data.data)])
  db.exec('COMMIT')

  postMessage({ type: 'INSERT_SUCCESS' })

  console.log('INSERT_ONE_SUCCESS')
}

let canInsert = true
async function insertMany(datas) {
  console.log('INSERT_MANY_BEGIN', memorySizeOf(datas))
  console.time('insertMany')

  canInsert = false

  let db = await getDatabase()

  db.exec('BEGIN TRANSACTION')
  let stmt = db.prepare('INSERT INTO charts (timestamp, data) VALUES (?, ?)')

  for (let result of datas) {
    stmt.run([result.timestamp, JSON.stringify(result.data)])
  }
  db.exec('COMMIT')

  postMessage({ type: 'INSERT_SUCCESS' })

  canInsert = true
  console.log('INSERT_MANY_SUCCESS')
  console.timeEnd('insertMany')
}

async function queryOne(timestamp) {
  let db = await getDatabase()

  const stmt = db.prepare(
    `SELECT data FROM charts WHERE timestamp=:aval LIMIT 0,1`
  )
  const result = stmt.getAsObject({ ':aval': timestamp })

  postMessage({ type: 'QUERY_SUCCESS', data: result })
}

let pool = []
async function scheduleInsert(data) {
  pool.push(data)

  while (pool.length > 10 && canInsert) {
    const tmp = [...pool]
    pool.length = 0
    await insertMany(tmp)
  }
}

// setInterval(async () => {
//   let db = await getDatabase()

//   const result = db.exec(`SELECT * FROM charts`)

//   console.log('result>>>>', result)
// }, 3000)

onmessage = msg => {
  switch (msg.data.type) {
    case 'INSERT_ONE':
      insertOne(msg.data.data)
      break

    case 'SCHEDULE_INSERT':
      scheduleInsert(msg.data.data)
      break

    case 'INSERT_MANY':
      insertMany(msg.data.data)
      break

    case 'QUERY_ONE':
      queryOne(msg.data.data)
      break
    default:
      break
  }
}
